#!/usr/bin/env python3
"""
执行器服务
独立的任务执行服务，负责执行脚本和发送钉钉消息
"""

import os
import sys
import json
import time
import platform
import subprocess
import threading
import shutil
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, send_file
import requests
import logging
from logging.handlers import RotatingFileHandler
import traceback

# 设置时区（仅在Unix/Linux系统上可用）
if platform.system() != 'Windows':
    time.tzset()

# 添加项目根目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from models import db, ScheduledTask, CustomTask, TaskExecutionLog, CustomTaskLog, User, DingTalkRobot, Script, CustomScript, ScriptVersion, BackgroundInstallationTask, PackageInstallationLog
from system_scripts.task_engine import TaskEngine
from system_scripts.custom_task_engine import CustomTaskEngine

# 导入包管理器
try:
    from system_scripts.package_manager import PackageManager
    package_manager = None  # 将在后面初始化
except ImportError as e:
    app.logger.error(f"导入PackageManager失败: {str(e)}")
    package_manager = None

# 配置日志
if not os.path.exists('logs'):
    os.mkdir('logs')
# 检查是否通过start_services.py启动（会有标准输出重定向到日志文件）
is_started_by_service_manager = sys.stdout.name != '<stdout>'
if not is_started_by_service_manager:
    log_date = datetime.now().strftime('%Y-%m-%d')
    file_handler = RotatingFileHandler(f'logs/executor_service_{log_date}.log', maxBytes=10240, backupCount=10)
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    ))
    file_handler.setLevel(logging.INFO)
else:
    # 如果是通过服务管理器启动，则不创建额外的日志文件
    file_handler = logging.StreamHandler()
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    ))
    file_handler.setLevel(logging.INFO)

app = Flask(__name__)

# 数据库配置 - 使用外部MySQL
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from config.mysql_config import MySQLConfig
mysql_config = MySQLConfig()
mysql_config.validate_config()

app.config['SQLALCHEMY_DATABASE_URI'] = mysql_config.get_database_uri()
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = mysql_config.get_engine_options()
app.logger.info(f"执行器服务使用MySQL数据库: {mysql_config.MYSQL_HOST}:{mysql_config.MYSQL_PORT}/{mysql_config.MYSQL_DATABASE}")

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 初始化数据库
db.init_app(app)

# 配置日志
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)

# 配置task_engine的日志
from system_scripts.task_engine import logger as task_engine_logger
task_engine_logger.addHandler(file_handler)
task_engine_logger.setLevel(logging.INFO)

# 任务引擎
task_engine = TaskEngine()
custom_task_engine = CustomTaskEngine()

# 初始化包管理器
try:
    if 'PackageManager' in globals():
        package_manager = PackageManager()
        app.logger.info("PackageManager初始化成功")
    else:
        package_manager = None
        app.logger.warning("PackageManager类未导入，包管理功能将使用回退模式")
except Exception as e:
    app.logger.error(f"初始化PackageManager失败: {str(e)}")
    package_manager = None

def _send_dingtalk_message(messages, webhook, secret, at_persons='', task_id=None):
    """发送钉钉消息"""
    start_time = datetime.now()
    try:
        import requests
        import time
        import hmac
        import hashlib
        import base64
        import urllib.parse
        
        if not messages or not isinstance(messages, list):
            app.logger.error("消息列表为空或格式错误")
            return False
        
        # 发送所有消息
        success_count = 0
        total_count = len(messages)
        error_messages = []
        
        for i, msg in enumerate(messages):
            # 将消息转换为字符串格式
            if isinstance(msg, str):
                message_content = msg
            else:
                # 如果不是字符串，转换为字符串
                message_content = str(msg)
            
            # 计算签名
            timestamp = str(round(time.time() * 1000))
            secret_enc = secret.encode('utf-8')
            string_to_sign = f"{timestamp}\n{secret}"
            string_to_sign_enc = string_to_sign.encode('utf-8')
            hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
            sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
            
            url = f"{webhook}&timestamp={timestamp}&sign={sign}"
            
            headers = {'Content-Type': 'application/json'}
            data = {
                "msgtype": "text",
                "text": {"content": message_content}
            }
            
            # 如果有@人配置，添加@功能
            if at_persons and at_persons.strip():
                phone_numbers = [phone.strip() for phone in at_persons.split(',') if phone.strip()]
                if phone_numbers:
                    data["at"] = {
                        "atMobiles": phone_numbers,
                        "isAtAll": False
                    }
            
            response = requests.post(url, headers=headers, json=data, timeout=10)
            
            if response.status_code == 200:
                result = response.json()
                if result.get('errcode') == 0:
                    success_count += 1
                    app.logger.info(f"第{i+1}条消息发送成功")
                else:
                    error_msg = f"第{i+1}条消息发送失败: {result}"
                    app.logger.error(error_msg)
                    error_messages.append(error_msg)
            else:
                error_msg = f"第{i+1}条消息HTTP请求失败: {response.status_code}"
                app.logger.error(error_msg)
                error_messages.append(error_msg)
        
        # 创建钉钉消息发送日志记录
        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        
        if task_id:
            try:
                dingtalk_log_content = f"钉钉消息发送详情:\n"
                if at_persons:
                    dingtalk_log_content += f"@人配置: {at_persons}\n"
                for i, msg in enumerate(messages, 1):
                    # 强制转换为字符串
                    msg_str = str(msg) if not isinstance(msg, str) else msg
                    dingtalk_log_content += f"第{i}条消息: {msg_str[:100]}{'...' if len(msg_str) > 100 else ''}\n"
                
                if error_messages:
                    dingtalk_log_content += f"\n发送失败的消息:\n" + "\n".join(error_messages)
                else:
                    dingtalk_log_content += "\n所有消息发送成功"
                    
                dingtalk_execution_log = TaskExecutionLog(
                    task_id=task_id,
                    start_time=start_time,
                    end_time=end_time,
                    duration=duration,
                    status='成功' if success_count == total_count else '失败',
                    log_content=dingtalk_log_content,
                    log_type='dingtalk_log'
                )
                db.session.add(dingtalk_execution_log)
                db.session.commit()
            except Exception as e:
                app.logger.error(f"保存钉钉消息日志失败: {str(e)}")
        
        return success_count == total_count
        
    except Exception as e:
        app.logger.error(f"发送钉钉消息异常: {str(e)}")
        app.logger.error(f"异常详情: {traceback.format_exc()}")
        return False

# 文件管理相关配置
CUSTOM_SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), '..', 'custom_scripts')
if not os.path.exists(CUSTOM_SCRIPTS_DIR):
    os.makedirs(CUSTOM_SCRIPTS_DIR)

def execute_dingtalk_task_async(task_id):
    """异步执行钉钉任务"""
    def _execute():
        with app.app_context():
            try:
                task = ScheduledTask.query.get(task_id)
                if not task or not task.is_active:
                    app.logger.warning(f'任务 {task_id} 不存在或未启用')
                    return
                
                app.logger.info(f'开始执行钉钉任务: {task.name} (ID: {task_id})')
                
                # 更新任务状态
                task.status = 'running'
                task.last_run_time = datetime.now()
                db.session.commit()
                app.logger.error(f'[DEBUG] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                
                # 解析任务参数
                task_params = {}
                if task.params:
                    try:
                        task_params = json.loads(task.params)
                    except json.JSONDecodeError:
                        app.logger.warning(f"任务 {task.name} 参数格式错误，使用空参数")
                        task_params = {}
                
                # 获取@人配置
                robot_at_configs = task.get_robot_at_configs()
                
                # 获取关联的机器人信息
                robots = task.robots
                if robots:
                    # 执行脚本
                    result = task_engine.execute_script_only(task.script_path, task_params, task.id)
                    app.logger.error(f'[DEBUG] 脚本执行返回: {result}')
                    # 默认状态
                    final_status = None
                    exec_time = datetime.now()  # 记录本次执行时间
                    # 判断脚本是否执行失败
                    if isinstance(result, dict) and 'error' in result:
                        app.logger.error(f'任务 {task.name} 脚本执行失败: {result}')
                        final_status = '失败'
                        task.last_run_status = final_status
                        task.last_run_time = exec_time
                        task.status = 'pending'
                        app.logger.error(f'[COMMIT-BEFORE] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                        db.session.commit()
                        app.logger.error(f'[COMMIT-AFTER] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                        app.logger.info(f'钉钉任务执行完成: {task.name}, 状态: {task.last_run_status}')
                        app.logger.error(f'[RETURN] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                        return
                    else:
                        # 向所有机器人发送消息
                        success_count = 0
                        total_count = len(robots)
                        # 如果脚本返回了消息，才发送消息
                        if result and len(result) > 0:
                            for robot in robots:
                                try:
                                    # 获取该机器人的@人配置
                                    robot_at_persons = robot_at_configs.get(str(robot.id), '')
                                    app.logger.info(f"向机器人 {robot.name} 发送消息，@人配置: {robot_at_persons}")
                                    app.logger.info(f"发送脚本返回的消息: {result}")
                                    send_result = _send_dingtalk_message(
                                        result, robot.webhook, robot.secret, at_persons=robot_at_persons, task_id=task_id
                                    )
                                    app.logger.info(f"消息发送结果: {send_result}")
                                    if send_result:
                                        success_count += 1
                                    else:
                                        app.logger.error(f"消息发送失败，机器人: {robot.name}")
                                except Exception as e:
                                    app.logger.error(f"发送消息到机器人 {robot.name} 失败: {str(e)}")
                                    app.logger.error(f"异常详情: {traceback.format_exc()}")
                        else:
                            # 脚本返回空列表，不发送消息，但任务执行成功
                            app.logger.info(f"脚本返回空列表，不发送消息")
                            success_count = total_count  # 所有机器人都算成功，因为没有消息需要发送
                        # 只在这里赋值
                        if success_count == total_count:
                            final_status = '成功'
                        else:
                            final_status = f'部分成功({success_count}/{total_count})'
                    # 只在最后赋值和提交
                    app.logger.info(f'最终任务状态: {final_status}')
                    task.last_run_status = final_status
                    task.last_run_time = exec_time  # 无论成功失败都更新
                    task.status = 'pending'
                    app.logger.error(f'[COMMIT-BEFORE] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                    db.session.commit()
                    app.logger.error(f'[COMMIT-AFTER] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                    app.logger.info(f'钉钉任务执行完成: {task.name}, 状态: {task.last_run_status}')
                    app.logger.error(f'[RETURN] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                else:
                    app.logger.error(f'任务 {task.name} 未配置机器人')
                    task.last_run_status = '失败'
                    task.last_run_time = datetime.now()
                    task.status = 'pending'
                    app.logger.error(f'[COMMIT-BEFORE] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                    db.session.commit()
                    app.logger.error(f'[COMMIT-AFTER] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                    app.logger.info(f'钉钉任务执行完成: {task.name}, 状态: {task.last_run_status}')
                    app.logger.error(f'[RETURN] 任务 {task.id} 状态: {task.last_run_status}, 时间: {task.last_run_time}')
                
            except Exception as e:
                app.logger.error(f"执行钉钉任务 {task_id} 失败: {str(e)}")
                if task:
                    task.status = 'failed'
                    task.last_run_status = '失败'
                    task.last_run_time = datetime.now()
                    db.session.commit()
                    app.logger.error(f'[EXCEPT] 任务 {task.id} 状态已写入: {task.last_run_status}, 时间: {task.last_run_time}')
    
    # 启动异步线程
    thread = threading.Thread(target=_execute)
    thread.daemon = True
    thread.start()

def execute_custom_task_async(task_id):
    """异步执行自定义任务"""
    def _execute():
        with app.app_context():
            try:
                task = CustomTask.query.get(task_id)
                if not task or not task.is_active:
                    app.logger.warning(f'自定义任务 {task_id} 不存在或未启用')
                    return
                
                app.logger.info(f'开始执行自定义任务: {task.name} (ID: {task_id})')
                
                # 更新任务状态
                task.status = 'running'
                task.last_run_time = datetime.now()
                db.session.commit()
                
                # 检查脚本是否存在
                if not task.script or not os.path.exists(task.script.file_path):
                    app.logger.error(f'自定义任务 {task.name} 的脚本不存在')
                    task.status = 'failed'
                    task.last_run_status = '失败'
                    db.session.commit()
                    return
                
                # 执行任务
                success = custom_task_engine.execute_script(task.script.file_path, task_id)
                
                # 更新任务状态
                task.status = 'pending'
                task.last_run_status = '成功' if success else '失败'
                db.session.commit()
                
                app.logger.info(f'自定义任务执行完成: {task.name}, 状态: {task.last_run_status}')
                
            except Exception as e:
                app.logger.error(f"执行自定义任务 {task_id} 失败: {str(e)}")
                if task:
                    task.status = 'failed'
                    task.last_run_status = '失败'
                    db.session.commit()
    
    # 启动异步线程
    thread = threading.Thread(target=_execute)
    thread.daemon = True
    thread.start()

# 基础API路由
@app.route('/status')
def get_status():
    """获取执行器状态"""
    return jsonify({
        'status': 'running',
        'timestamp': datetime.now().isoformat(),
        'service': 'executor'
    })

@app.route('/api/health')
def health_check():
    """健康检查端点"""
    return jsonify({'status': 'ok'})

@app.route('/api/tasks/<int:task_id>/execute', methods=['POST'])
def execute_task(task_id):
    """执行钉钉任务"""
    try:
        # 异步执行任务
        execute_dingtalk_task_async(task_id)
        return jsonify({'success': True, 'message': '任务已开始执行'})
    except Exception as e:
        app.logger.error(f"获取所有自定义文件夹失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/custom_tasks/<int:task_id>/execute', methods=['POST'])
def execute_custom_task(task_id):
    """执行自定义任务"""
    try:
        # 异步执行任务
        execute_custom_task_async(task_id)
        return jsonify({'success': True, 'message': '任务已开始执行'})
    except Exception as e:
        app.logger.error(f"获取所有自定义文件夹失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/tasks/<int:task_id>/run', methods=['POST'])
def run_task(task_id):
    """手动执行任务"""
    try:
        # 先检查任务是否存在且已启用
        with app.app_context():
            task = ScheduledTask.query.get(task_id)
            if not task:
                return jsonify({'error': '任务不存在', 'message': '任务不存在'}), 404
            if not task.is_active:
                return jsonify({'error': '任务未启用', 'message': '任务已禁用，无法执行'}), 400
        
        # 异步执行任务
        execute_dingtalk_task_async(task_id)
        return jsonify({'success': True, 'message': '任务已开始执行'})
    except Exception as e:
        app.logger.error(f"执行任务失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/logs/<int:task_id>')
def get_task_logs(task_id):
    """获取任务日志"""
    try:
        with app.app_context():
            # 获取钉钉任务日志
            logs = TaskExecutionLog.query.filter_by(task_id=task_id).order_by(
                TaskExecutionLog.start_time.desc()
            ).limit(50).all()
            
            log_list = []
            for log in logs:
                log_list.append({
                    'id': log.id,
                    'start_time': log.start_time.isoformat() if log.start_time else None,
                    'end_time': log.end_time.isoformat() if log.end_time else None,
                    'duration': log.duration,
                    'status': log.status,
                    'log_content': log.log_content
                })
            
            return jsonify(log_list)
    except Exception as e:
        app.logger.error(f"获取用户自定义文件夹失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/custom_logs/<int:task_id>')
@app.route('/api/custom_tasks/<int:task_id>/logs', methods=['GET'])
def get_custom_task_logs(task_id):
    """获取自定义任务日志"""
    try:
        with app.app_context():
            # 获取自定义任务日志
            logs = CustomTaskLog.query.filter_by(task_id=task_id).order_by(
                CustomTaskLog.start_time.desc()
            ).limit(50).all()
            
            log_list = []
            for log in logs:
                log_list.append({
                    'id': log.id,
                    'start_time': log.start_time.isoformat() if log.start_time else None,
                    'end_time': log.end_time.isoformat() if log.end_time else None,
                    'duration': log.duration,
                    'status': log.status,
                    'output': log.output,
                    'error_message': log.error_message
                })
            
            return jsonify(log_list)
    except Exception as e:
        app.logger.error(f"获取自定义任务日志失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

# 文件管理API
@app.route('/api/list_all_custom_folders')
def list_all_custom_folders():
    """获取所有自定义文件夹（管理员）"""
    try:
        with app.app_context():
            folders = []
            if os.path.exists(CUSTOM_SCRIPTS_DIR):
                for user_dir in os.listdir(CUSTOM_SCRIPTS_DIR):
                    user_path = os.path.join(CUSTOM_SCRIPTS_DIR, user_dir)
                    if os.path.isdir(user_path):
                        user = User.query.filter_by(username=user_dir).first()
                        for folder_name in os.listdir(user_path):
                            # 过滤隐藏文件夹（以点开头的文件夹）
                            if folder_name.startswith('.'):
                                continue
                                
                            folder_path = os.path.join(user_path, folder_name)
                            if os.path.isdir(folder_path):
                                # 统计脚本数量
                                script_count = len([f for f in os.listdir(folder_path) if f.endswith('.py')])
                                
                                # 检查是否有受保护的脚本
                                has_protected_scripts = False
                                protected_script_name = None
                                
                                # 使用os.walk递归遍历文件夹及其子文件夹
                                for root, dirs, files in os.walk(folder_path):
                                    for file in files:
                                        if file.endswith('.py'):
                                            file_path = os.path.join(root, file)
                                            # 通过文件路径查询脚本，这与delete_custom_folder函数中的查询方式一致
                                            custom_script = CustomScript.query.filter_by(file_path=file_path).first()
                                            if custom_script:
                                                has_protected_scripts = True
                                                protected_script_name = file
                                                break
                                    if has_protected_scripts:
                                        break
                                
                                folders.append({
                                    'name': folder_name,
                                    'owner_username': user_dir,
                                    'owner_id': user.id if user else None,
                                    'path': folder_path,
                                    'relative_path': f'{user_dir}/{folder_name}',
                                    'script_count': script_count,
                                    'created_time': datetime.fromtimestamp(os.path.getctime(folder_path)).strftime('%Y-%m-%d %H:%M:%S'),
                                    'has_protected_scripts': has_protected_scripts,
                                    'protected_script_name': protected_script_name
                                })
            return jsonify({'folders': folders})
    except Exception as e:
        app.logger.error(f"获取所有自定义文件夹失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/list_custom_folders')
def list_custom_folders():
    """获取用户自定义文件夹"""
    try:
        with app.app_context():
            # 从请求头获取用户信息（由web_service传递）
            user_id = request.headers.get('X-User-ID')
            username = request.headers.get('X-Username')
            
            folders = []
            
            # 如果没有用户信息，尝试从session或其他方式获取
            if not username:
                # 这里可以根据实际情况调整获取用户信息的方式
                # 暂时返回空列表，但不报错
                return jsonify({'folders': []})
            
            user_dir = os.path.join(CUSTOM_SCRIPTS_DIR, username)
            if os.path.exists(user_dir):
                user = User.query.filter_by(username=username).first()
                for folder_name in os.listdir(user_dir):
                    # 过滤隐藏文件夹（以点开头的文件夹）
                    if folder_name.startswith('.'):
                        continue
                        
                    folder_path = os.path.join(user_dir, folder_name)
                    if os.path.isdir(folder_path):
                        # 统计脚本数量
                        script_count = len([f for f in os.listdir(folder_path) if f.endswith('.py')])
                        
                        # 检查是否有受保护的脚本
                        has_protected_scripts = False
                        protected_script_name = None
                        
                        # 使用os.walk递归遍历文件夹及其子文件夹
                        for root, dirs, files in os.walk(folder_path):
                            for file in files:
                                if file.endswith('.py'):
                                    file_path = os.path.join(root, file)
                                    # 通过文件路径查询脚本，这与delete_custom_folder函数中的查询方式一致
                                    custom_script = CustomScript.query.filter_by(file_path=file_path).first()
                                    if custom_script:
                                        has_protected_scripts = True
                                        protected_script_name = file
                                        break
                            if has_protected_scripts:
                                break
                        
                        folders.append({
                            'name': folder_name,
                            'owner_username': username,
                            'owner_id': user.id if user else None,
                            'path': folder_path,
                            'relative_path': f'{username}/{folder_name}',
                            'script_count': script_count,
                            'created_time': datetime.fromtimestamp(os.path.getctime(folder_path)).strftime('%Y-%m-%d %H:%M:%S'),
                            'has_protected_scripts': has_protected_scripts,
                            'protected_script_name': protected_script_name
                        })
            
            return jsonify({'folders': folders})
    except Exception as e:
        app.logger.error(f"获取用户自定义文件夹失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/create_custom_folder', methods=['POST'])
def create_custom_folder():
    """创建自定义文件夹"""
    try:
        data = request.json
        folder_name = data.get('folder_name')
        user_id = data.get('user_id')
        
        if not folder_name or not user_id:
            return jsonify({'error': '缺少必要参数'}), 400
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            user_dir = os.path.join(CUSTOM_SCRIPTS_DIR, user.username)
            if not os.path.exists(user_dir):
                os.makedirs(user_dir)
            
            folder_path = os.path.join(user_dir, folder_name)
            if os.path.exists(folder_path):
                return jsonify({'error': '文件夹已存在'}), 400
            
            os.makedirs(folder_path)
            return jsonify({'success': True, 'message': '文件夹创建成功'})
    except Exception as e:
        app.logger.error(f"创建自定义文件夹失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/delete_custom_folder/<folder_name>', methods=['DELETE'])
def delete_custom_folder(folder_name):
    """删除自定义文件夹"""
    try:
        user_id = request.args.get('user_id')
        if not user_id:
            return jsonify({'error': '缺少用户ID'}), 400
        
        with app.app_context():
            # 优先检查按用户ID构建的路径（处理历史遗留数据）
            folder_path_by_id = os.path.join(CUSTOM_SCRIPTS_DIR, str(user_id), folder_name)
            
            if os.path.exists(folder_path_by_id):
                # 按用户ID的路径存在，使用这个路径
                folder_path = folder_path_by_id
            else:
                # 按用户ID的路径不存在，尝试按用户名构建路径
                user = User.query.get(user_id)
                if user:
                    folder_path = os.path.join(CUSTOM_SCRIPTS_DIR, user.username, folder_name)
                else:
                    # 用户不存在，返回错误
                    return jsonify({'error': '文件夹不存在'}), 404
            
            if not os.path.exists(folder_path):
                return jsonify({'error': '文件夹不存在'}), 404
            
            # 检查文件夹内是否有已注册的脚本
            has_protected_scripts = False
            protected_script_name = None
            
            # 使用os.walk递归遍历文件夹及其子文件夹
            for root, dirs, files in os.walk(folder_path):
                for file in files:
                    file_path = os.path.join(root, file)
                    # 查询数据库检查文件是否已注册为脚本
                    custom_script = CustomScript.query.filter_by(file_path=file_path).first()
                    if custom_script:
                        has_protected_scripts = True
                        protected_script_name = file
                        break
                if has_protected_scripts:
                    break
            
            # 如果文件夹内包含已注册的脚本，返回错误
            if has_protected_scripts:
                app.logger.warning(f"尝试删除包含已注册脚本的文件夹: {folder_path}, 脚本: {protected_script_name}")
                return jsonify({'error': f'文件夹内包含已注册的脚本 {protected_script_name}，请先删除脚本', 'success': False}), 400
            
            # 安全删除文件夹
            shutil.rmtree(folder_path)
            app.logger.info(f"成功删除文件夹: {folder_path}")
            return jsonify({'success': True, 'message': '文件夹删除成功'})
    except Exception as e:
        app.logger.error(f"删除文件夹失败: {str(e)}")
        return jsonify({'error': str(e), 'success': False}), 500

# 包管理API
import subprocess
import pkg_resources
from threading import Thread
import time

# 全局变量存储包管理状态
package_progress = {
    'status': 'idle',
    'progress': 0,
    'message': '',
    'current_package': ''
}

def run_package_command(command, package_name=None, user_id=None, package_version=None):
    """运行包管理命令"""
    global package_progress
    
    try:
        # 初始化进度信息
        package_progress['status'] = 'running'
        package_progress['progress'] = 0
        package_progress['message'] = f'正在执行: {command}'
        package_progress['current_package'] = package_name or ''
        package_progress['progress_messages'] = []
        package_progress['elapsed_time'] = 0
        
        # 确定操作类型
        if 'install' in command and 'upgrade' not in command:
            operation = 'install'
        elif 'upgrade' in command or ('install' in command and 'upgrade' in command):
            operation = 'upgrade'
        elif 'uninstall' in command:
            operation = 'uninstall'
        else:
            operation = 'unknown'
            
        package_progress['operation'] = operation
        
        start_time = time.time()
        
        # 添加开始消息
        package_progress['progress_messages'].append({
            'timestamp': time.time(),
            'message': f'开始{operation}包: {package_name or "未知"}',
            'level': 'info'
        })
        
        # 优先使用PackageManager执行操作
        if package_manager and package_name:
            try:
                app.logger.info(f"使用PackageManager执行{operation}操作: {package_name}")
                
                if operation == 'install':
                    result = package_manager.install_package(package_name, package_version)
                elif operation == 'upgrade':
                    result = package_manager.upgrade_package(package_name)
                elif operation == 'uninstall':
                    result = package_manager.uninstall_package(package_name)
                else:
                    # 回退到直接执行命令
                    app.logger.warning(f"未知操作类型: {operation}, 回退到直接执行命令")
                    result = execute_command_fallback(command)
                
                # 处理PackageManager的结果
                if result.get('success'):
                    package_progress['status'] = 'success'
                    package_progress['progress'] = 100
                    package_progress['message'] = '操作完成'
                    status = 'success'
                    error_output = None
                    
                    # 添加成功消息
                    package_progress['progress_messages'].append({
                        'timestamp': time.time(),
                        'message': result.get('message', '操作成功完成'),
                        'level': 'success'
                    })
                else:
                    package_progress['status'] = 'failed'
                    package_progress['progress'] = 100
                    package_progress['message'] = result.get('message', '操作失败')
                    status = 'failed'
                    error_output = result.get('error', '未知错误')
                    
                    # 添加失败消息
                    package_progress['progress_messages'].append({
                        'timestamp': time.time(),
                        'message': result.get('message', '操作失败'),
                        'level': 'error'
                    })
                
                # 添加详细日志
                if 'logs' in result:
                    for log_entry in result['logs']:
                        package_progress['progress_messages'].append({
                            'timestamp': time.time(),
                            'message': log_entry.get('message', ''),
                            'level': log_entry.get('level', 'info')
                        })
                        
            except Exception as pm_error:
                app.logger.error(f"PackageManager执行失败: {str(pm_error)}")
                # 回退到直接执行命令
                app.logger.info("回退到直接执行pip命令")
                result = execute_command_fallback(command)
                
                if result.get('success'):
                    package_progress['status'] = 'success'
                    package_progress['progress'] = 100
                    package_progress['message'] = '操作完成'
                    status = 'success'
                    error_output = None
                else:
                    package_progress['status'] = 'failed'
                    package_progress['progress'] = 100
                    package_progress['message'] = result.get('message', '操作失败')
                    status = 'failed'
                    error_output = result.get('error', str(pm_error))
        else:
            # 没有PackageManager或没有包名，回退到直接执行命令
            app.logger.info("使用直接执行pip命令模式")
            result = execute_command_fallback(command)
            
            if result.get('success'):
                package_progress['status'] = 'success'
                package_progress['progress'] = 100
                package_progress['message'] = '操作完成'
                status = 'success'
                error_output = None
            else:
                package_progress['status'] = 'failed'
                package_progress['progress'] = 100
                package_progress['message'] = result.get('message', '操作失败')
                status = 'failed'
                error_output = result.get('error', '未知错误')
        
        # 更新最终状态
        end_time = time.time()
        duration = end_time - start_time
        package_progress['elapsed_time'] = duration
            
        # 创建包安装日志记录
        if user_id and package_name:
            try:
                with app.app_context():
                    # 收集所有输出消息
                    output_messages = []
                    for msg in package_progress.get('progress_messages', []):
                        output_messages.append(f"[{msg['level']}] {msg['message']}")
                    output_content = '\n'.join(output_messages)
                    
                    log = PackageInstallationLog(
                        package_name=package_name,
                        package_version=package_version or 'unknown',
                        operation=package_progress['operation'],
                        status=status,
                        output=output_content,
                        error_output=error_output,
                        user_id=user_id,
                        duration=duration
                    )
                    db.session.add(log)
                    db.session.commit()
                    app.logger.info(f"已创建包安装日志: {package_name}, 状态: {status}, 用户: {user_id}")
            except Exception as log_error:
                app.logger.error(f"创建包安装日志失败: {str(log_error)}")
            
    except Exception as e:
        end_time = time.time()
        duration = end_time - start_time
        
        package_progress['status'] = 'failed'
        package_progress['progress'] = 100
        package_progress['message'] = f'操作异常: {str(e)}'
        # 添加异常消息
        if 'progress_messages' not in package_progress:
            package_progress['progress_messages'] = []
        package_progress['progress_messages'].append({
            'timestamp': time.time(),
            'message': f'操作异常: {str(e)}',
            'level': 'error'
        })
        
        # 创建包安装日志记录（异常情况）
        if user_id and package_name:
            try:
                with app.app_context():
                    # 收集所有输出消息
                    output_messages = []
                    for msg in package_progress.get('progress_messages', []):
                        output_messages.append(f"[{msg['level']}] {msg['message']}")
                    output_content = '\n'.join(output_messages)
                    
                    log = PackageInstallationLog(
                        package_name=package_name,
                        package_version=package_version or 'unknown',
                        operation=package_progress['operation'],
                        status='failed',
                        output=output_content,
                        error_output=f'操作异常: {str(e)}',
                        user_id=user_id,
                        duration=duration
                    )
                    db.session.add(log)
                    db.session.commit()
                    app.logger.info(f"已创建包安装日志（异常）: {package_name}, 用户: {user_id}")
            except Exception as log_error:
                app.logger.error(f"创建包安装日志失败: {str(log_error)}")

def execute_command_fallback(command):
    """回退方法：直接执行pip命令"""
    try:
        app.logger.info(f"使用回退方法执行命令: {command}")
        
        # 为pip命令添加详细输出参数
        if command.startswith('pip '):
            if '--verbose' not in command:
                command += ' --verbose'
            if '--progress-bar' not in command and 'install' in command:
                command += ' --progress-bar on'
        
        # 执行命令
        process = subprocess.Popen(
            command,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            bufsize=1
        )
        
        output_lines = []
        
        # 实时读取输出
        for line in iter(process.stdout.readline, ''):
            if line:
                line = line.strip()
                output_lines.append(line)
                
                # 根据输出内容判断消息级别
                level = 'info'
                if any(keyword in line.lower() for keyword in ['error', 'failed', 'exception']):
                    level = 'error'
                elif any(keyword in line.lower() for keyword in ['warning', 'warn']):
                    level = 'warning'
                elif any(keyword in line.lower() for keyword in ['successfully', 'complete', 'installed']):
                    level = 'success'
                
                # 更新进度消息
                if 'progress_messages' not in package_progress:
                    package_progress['progress_messages'] = []
                
                package_progress['progress_messages'].append({
                    'timestamp': time.time(),
                    'message': line,
                    'level': level
                })
                
                # 限制消息数量
                if len(package_progress['progress_messages']) > 100:
                    package_progress['progress_messages'] = package_progress['progress_messages'][-100:]
                
                # 根据输出内容更新进度
                if 'downloading' in line.lower():
                    package_progress['progress'] = min(package_progress.get('progress', 0) + 5, 30)
                elif 'collecting' in line.lower():
                    package_progress['progress'] = min(package_progress.get('progress', 0) + 10, 50)
                elif 'building' in line.lower():
                    package_progress['progress'] = min(package_progress.get('progress', 0) + 15, 70)
                elif 'installing' in line.lower():
                    package_progress['progress'] = min(package_progress.get('progress', 0) + 20, 90)
        
        # 等待命令完成
        return_code = process.wait()
        
        # 返回结果
        return {
            'success': return_code == 0,
            'output': '\n'.join(output_lines),
            'return_code': return_code
        }
        
    except Exception as e:
        app.logger.error(f"回退命令执行失败: {str(e)}")
        return {
            'success': False,
            'output': f'命令执行异常: {str(e)}',
            'return_code': -1
        }

@app.route('/api/set_mirror', methods=['POST'])
def set_mirror():
    """设置镜像源"""
    try:
        data = request.json
        mirror_url = data.get('mirror_url')
        mirror_name = data.get('mirror_name')
        
        # 镜像源映射
        mirror_mapping = {
            "官方源": "https://pypi.org/simple/",
            "清华大学": "https://pypi.tuna.tsinghua.edu.cn/simple/",
            "阿里云": "https://mirrors.aliyun.com/pypi/simple/",
            "中国科技大学": "https://pypi.mirrors.ustc.edu.cn/simple/",
            "豆瓣": "https://pypi.douban.com/simple/",
            "华为云": "https://repo.huaweicloud.com/repository/pypi/simple/",
            "腾讯云": "https://mirrors.cloud.tencent.com/pypi/simple/"
        }
        
        # 如果提供了镜像源名称，则转换为URL
        if mirror_name and mirror_name in mirror_mapping:
            mirror_url = mirror_mapping[mirror_name]
            app.logger.info(f"使用镜像源名称: {mirror_name}, URL: {mirror_url}")
        
        if not mirror_url:
            return jsonify({'error': '缺少有效的镜像源信息'}), 400
        
        # 创建pip配置文件
        pip_config_dir = os.path.expanduser('~/.pip')
        if not os.path.exists(pip_config_dir):
            os.makedirs(pip_config_dir)
        
        pip_config_file = os.path.join(pip_config_dir, 'pip.conf')
        config_content = f"""[global]
index-url = {mirror_url}
trusted-host = {mirror_url.replace('https://', '').replace('http://', '')}
"""
        
        with open(pip_config_file, 'w') as f:
            f.write(config_content)
        
        return jsonify({'success': True, 'message': '镜像源设置成功'})
    except Exception as e:
        app.logger.error(f"设置镜像源失败: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/install_package', methods=['POST'])
def install_package():
    """安装包"""
    try:
        data = request.json
        package_name = data.get('package_name')
        user_id = data.get('user_id')
        package_version = data.get('package_version')
        
        if not package_name:
            return jsonify({'error': '缺少包名'}), 400
        
        # 异步执行安装
        thread = Thread(target=run_package_command, args=(f'pip install {package_name}', package_name, user_id, package_version))
        thread.daemon = True
        thread.start()
        
        return jsonify({'success': True, 'message': f'开始安装包 {package_name}'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/upgrade_package', methods=['POST'])
def upgrade_package():
    """升级包"""
    try:
        data = request.json
        package_name = data.get('package_name')
        user_id = data.get('user_id')
        package_version = data.get('package_version')
        
        if not package_name:
            return jsonify({'error': '缺少包名'}), 400
        
        # 异步执行升级
        thread = Thread(target=run_package_command, args=(f'pip install --upgrade {package_name}', package_name, user_id, package_version))
        thread.daemon = True
        thread.start()
        
        return jsonify({'success': True, 'message': f'开始升级包 {package_name}'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/uninstall_package', methods=['POST'])
def uninstall_package():
    """卸载包"""
    try:
        data = request.json
        package_name = data.get('package_name')
        user_id = data.get('user_id')
        package_version = data.get('package_version')
        
        if not package_name:
            return jsonify({'error': '缺少包名'}), 400
        
        # 异步执行卸载
        thread = Thread(target=run_package_command, args=(f'pip uninstall -y {package_name}', package_name, user_id, package_version))
        thread.daemon = True
        thread.start()
        
        return jsonify({'success': True, 'message': f'开始卸载包 {package_name}'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/refresh_packages', methods=['POST'])
def refresh_packages():
    """刷新包列表"""
    try:
        # 异步执行刷新
        thread = Thread(target=run_package_command, args=('pip list',))
        thread.daemon = True
        thread.start()
        
        return jsonify({'success': True, 'message': '开始刷新包列表'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/get_mirrors')
def get_mirrors():
    """获取镜像源列表"""
    try:
        mirrors = [
            {'name': 'PyPI官方', 'url': 'https://pypi.org/simple/'},
            {'name': '清华大学镜像', 'url': 'https://pypi.tuna.tsinghua.edu.cn/simple/'},
            {'name': '阿里云镜像', 'url': 'https://mirrors.aliyun.com/pypi/simple/'},
            {'name': '豆瓣镜像', 'url': 'https://pypi.douban.com/simple/'}
        ]
        return jsonify({'mirrors': mirrors})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/get_progress')
def get_progress():
    """获取进度"""
    try:
        return jsonify({
            'success': True,
            'progress': package_progress
        })
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/clear_progress', methods=['POST'])
def clear_progress():
    """清除进度"""
    try:
        global package_progress
        package_progress = {
            'status': 'idle',
            'progress': 0,
            'message': '',
            'current_package': ''
        }
        return jsonify({'success': True, 'message': '进度清除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 后台安装功能已移除

@app.route('/api/check_running_tasks')
def check_running_tasks():
    """检查运行中的任务"""
    try:
        # 检查包管理任务状态
        running_tasks = []
        if package_progress['status'] == 'running':
            running_tasks.append({
                'type': 'package_management',
                'message': package_progress['message'],
                'progress': package_progress['progress']
            })
        
        return jsonify({'running_tasks': running_tasks})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 用户管理API
@app.route('/api/delete_user/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    """删除用户"""
    try:
        force = request.args.get('force', 'false').lower() == 'true'
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            # 检查是否为管理员
            if user.is_admin:
                return jsonify({'error': '不能删除管理员用户'}), 400
            
            # 检查用户是否有依赖项
            if not force and user.has_dependencies():
                return jsonify({'error': '用户有依赖项，不能直接删除。请先转移依赖项或使用force参数强制删除。'}), 400
            
            # 如果是强制删除，先删除所有依赖项
            if force:
                # 删除钉钉机器人
                DingTalkRobot.query.filter_by(user_id=user_id).delete()
                
                # 删除钉钉任务
                ScheduledTask.query.filter_by(user_id=user_id).delete()
                
                # 删除自定义脚本
                CustomScript.query.filter_by(user_id=user_id).delete()
                
                # 删除自定义任务
                CustomTask.query.filter_by(user_id=user_id).delete()
                
                # 删除后台任务
                BackgroundInstallationTask.query.filter_by(user_id=user_id).delete()
                
                # 删除包安装日志
                PackageInstallationLog.query.filter_by(user_id=user_id).delete()
            
            # 删除用户
            db.session.delete(user)
            db.session.commit()
            return jsonify({'success': True, 'message': '用户删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/transfer_user_dependencies/<int:user_id>', methods=['POST'])
def transfer_user_dependencies(user_id):
    """转移用户依赖"""
    try:
        data = request.json
        target_user_id = data.get('target_user_id')
        if not target_user_id:
            return jsonify({'error': '缺少目标用户ID'}), 400
        
        with app.app_context():
            source_user = User.query.get(user_id)
            target_user = User.query.get(target_user_id)
            
            if not source_user or not target_user:
                return jsonify({'error': '用户不存在'}), 404
            
            # 转移钉钉机器人
            DingTalkRobot.query.filter_by(user_id=user_id).update({'user_id': target_user_id})
            
            # 转移钉钉任务
            ScheduledTask.query.filter_by(user_id=user_id).update({'user_id': target_user_id})
            
            # 转移自定义脚本
            CustomScript.query.filter_by(user_id=user_id).update({'user_id': target_user_id})
            
            # 转移自定义任务
            CustomTask.query.filter_by(user_id=user_id).update({'user_id': target_user_id})
            
            # 转移后台任务
            BackgroundInstallationTask.query.filter_by(user_id=user_id).update({'user_id': target_user_id})
            
            # 转移包安装日志
            PackageInstallationLog.query.filter_by(user_id=user_id).update({'user_id': target_user_id})
            
            # 提交更改
            db.session.commit()
            
            return jsonify({'success': True, 'message': '依赖转移成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 脚本管理API
@app.route('/api/delete_script/<int:script_id>', methods=['DELETE'])
def delete_script(script_id):
    """删除脚本"""
    try:
        with app.app_context():
            script = Script.query.get(script_id)
            if not script:
                return jsonify({'error': '脚本不存在'}), 404
            
            # 先删除关联的脚本版本记录
            ScriptVersion.query.filter_by(script_id=script_id).delete()
            
            # 删除文件
            if os.path.exists(script.path):
                os.remove(script.path)
            
            db.session.delete(script)
            db.session.commit()
            return jsonify({'success': True, 'message': '脚本删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/delete_custom_script/<int:script_id>', methods=['DELETE'])
def delete_custom_script(script_id):
    """删除自定义脚本"""
    try:
        with app.app_context():
            script = CustomScript.query.get(script_id)
            if not script:
                return jsonify({'error': '脚本不存在'}), 404
            
            # 删除文件
            if os.path.exists(script.file_path):
                os.remove(script.file_path)
            
            # 删除关联的自定义任务
            CustomTask.query.filter_by(script_id=script_id).delete()
            
            db.session.delete(script)
            db.session.commit()
            return jsonify({'success': True, 'message': '脚本删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/download_script/<int:script_id>')
def download_script(script_id):
    """下载脚本"""
    try:
        with app.app_context():
            script = Script.query.get(script_id)
            if not script:
                return jsonify({'error': '脚本不存在'}), 404
            
            if not os.path.exists(script.path):
                return jsonify({'error': '脚本文件不存在'}), 404
            
            return send_file(script.path, as_attachment=True, download_name=f"{script.name}.py")
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 机器人管理API
@app.route('/api/delete_robot/<int:robot_id>', methods=['DELETE'])
def delete_robot(robot_id):
    """删除机器人"""
    try:
        with app.app_context():
            robot = DingTalkRobot.query.get(robot_id)
            if not robot:
                return jsonify({'error': '机器人不存在'}), 404
            
            db.session.delete(robot)
            db.session.commit()
            return jsonify({'success': True, 'message': '机器人删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 自定义任务API
@app.route('/api/run_custom_task/<int:task_id>', methods=['POST'])
def run_custom_task(task_id):
    """运行自定义任务"""
    try:
        with app.app_context():
            task = CustomTask.query.get(task_id)
            if not task:
                return jsonify({'error': '任务不存在'}), 404
            if not task.is_active:
                return jsonify({'error': '任务未启用'}), 400
            if not task.script:
                return jsonify({'error': '未绑定脚本'}), 400
            if not os.path.exists(task.script.file_path):
                return jsonify({'error': '脚本文件不存在'}), 400
        execute_custom_task_async(task_id)
        return jsonify({'success': True, 'message': '任务已开始执行'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/toggle_custom_task/<int:task_id>', methods=['POST'])
def toggle_custom_task(task_id):
    """切换自定义任务状态"""
    try:
        with app.app_context():
            task = CustomTask.query.get(task_id)
            if not task:
                return jsonify({'error': '任务不存在'}), 404
            
            task.is_active = not task.is_active
            db.session.commit()
            return jsonify({'success': True, 'message': f'任务已{"启用" if task.is_active else "禁用"}'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/delete_custom_task/<int:task_id>', methods=['DELETE'])
def delete_custom_task(task_id):
    """删除自定义任务"""
    try:
        with app.app_context():
            task = CustomTask.query.get(task_id)
            if not task:
                return jsonify({'error': '任务不存在'}), 404
            
            # 删除任务关联的日志
            CustomTaskLog.query.filter_by(task_id=task_id).delete()
            
            db.session.delete(task)
            db.session.commit()
            return jsonify({'success': True, 'message': '任务删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 文件管理API
@app.route('/api/upload_files', methods=['POST'])
def upload_files():
    """上传文件"""
    try:
        if 'file' not in request.files:
            return jsonify({'error': '没有文件'}), 400
        
        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': '没有选择文件'}), 400
        
        # 获取用户信息
        user_id = request.form.get('user_id')
        if not user_id:
            return jsonify({'error': '缺少用户ID'}), 400
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            # 创建用户文件目录
            user_files_dir = os.path.join(CUSTOM_SCRIPTS_DIR, user.username)
            if not os.path.exists(user_files_dir):
                os.makedirs(user_files_dir)
            
            # 保存文件
            filename = file.filename
            file_path = os.path.join(user_files_dir, filename)
            file.save(file_path)
            
            return jsonify({'success': True, 'message': '文件上传成功', 'filename': filename})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/delete_file', methods=['POST'])
def delete_file():
    """删除文件"""
    try:
        data = request.json
        filename = data.get('filename')
        user_id = data.get('user_id')
        
        if not filename or not user_id:
            return jsonify({'error': '缺少必要参数'}), 400
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            file_path = os.path.join(CUSTOM_SCRIPTS_DIR, user.username, filename)
            if not os.path.exists(file_path):
                return jsonify({'error': '文件不存在'}), 404
            
            os.remove(file_path)
            return jsonify({'success': True, 'message': '文件删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/download_file')
def download_file():
    """下载文件"""
    try:
        filename = request.args.get('filename')
        user_id = request.args.get('user_id')
        
        if not filename or not user_id:
            return jsonify({'error': '缺少必要参数'}), 400
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            file_path = os.path.join(CUSTOM_SCRIPTS_DIR, user.username, filename)
            if not os.path.exists(file_path):
                return jsonify({'error': '文件不存在'}), 404
            
            return send_file(file_path, as_attachment=True, download_name=filename)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/view_file')
def view_file():
    """查看文件内容"""
    try:
        filename = request.args.get('filename')
        user_id = request.args.get('user_id')
        
        if not filename or not user_id:
            return jsonify({'error': '缺少必要参数'}), 400
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            file_path = os.path.join(CUSTOM_SCRIPTS_DIR, user.username, filename)
            if not os.path.exists(file_path):
                return jsonify({'error': '文件不存在'}), 404
            
            # 尝试以不同编码读取文本文件
            encodings = ['utf-8', 'gbk', 'latin-1', 'cp1252', 'iso-8859-1']
            content = None
            
            for encoding in encodings:
                try:
                    with open(file_path, 'r', encoding=encoding) as f:
                        content = f.read()
                    break
                except UnicodeDecodeError:
                    continue
            
            if content is None:
                app.logger.warning(f"view_file接口无法解码文件: {file_path}")
                return jsonify({'error': '文件编码不支持，无法预览'}), 400
            
            return jsonify({'content': content})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/list_files')
def list_files():
    """列出用户文件和目录"""
    try:
        user_id = request.args.get('user_id')
        path = request.args.get('path', '')
        
        if not user_id:
            return jsonify({'error': '缺少用户ID'}), 400
        
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            # 构建用户文件目录路径
            user_files_dir = os.path.join(CUSTOM_SCRIPTS_DIR, user.username)
            
            # 构建当前路径
            current_path = path.strip('/') if path else ''
            full_path = os.path.join(user_files_dir, current_path) if current_path else user_files_dir
            
            # 安全检查：确保路径在用户目录内
            if not os.path.normpath(full_path).startswith(os.path.normpath(user_files_dir)):
                return jsonify({'error': '无效的路径'}), 400
            
            files = []
            
            if os.path.exists(full_path):
                for filename in os.listdir(full_path):
                    # 过滤隐藏文件（以点开头的文件）
                    if filename.startswith('.'):
                        continue
                        
                    file_path = os.path.join(full_path, filename)
                    is_dir = os.path.isdir(file_path)
                    
                    # 获取文件信息
                    stat = os.stat(file_path)
                    
                    # 检查文件是否已注册为脚本
                    is_registered = False
                    has_protected_scripts = False
                    protected_script_name = None
                    
                    if not is_dir:
                        # 检查文件是否已注册为脚本
                        custom_script = CustomScript.query.filter_by(file_path=file_path).first()
                        is_registered = custom_script is not None
                    else:
                        # 检查文件夹内是否有已注册的脚本
                        for root, dirs, folder_files in os.walk(file_path):
                            for file in folder_files:
                                if file.endswith('.py'):
                                    script_path = os.path.join(root, file)
                                    custom_script = CustomScript.query.filter_by(file_path=script_path).first()
                                    if custom_script:
                                        has_protected_scripts = True
                                        protected_script_name = file
                                        break
                            if has_protected_scripts:
                                break
                    
                    files.append({
                        'name': filename,
                        'is_dir': is_dir,
                        'size': stat.st_size,
                        'created': datetime.fromtimestamp(stat.st_ctime).strftime('%Y-%m-%d %H:%M:%S'),
                        'modified': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
                        'is_registered': is_registered,
                        'has_protected_scripts': has_protected_scripts,
                        'protected_script_name': protected_script_name
                    })
                
                # 排序：文件夹在前，文件在后，按名称排序
                files.sort(key=lambda x: (not x['is_dir'], x['name'].lower()))
            
            return jsonify({'files': files})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 删除日志API
@app.route('/api/delete_logs', methods=['POST'])
def delete_logs():
    """删除日志"""
    try:
        data = request.json
        log_type = data.get('log_type', 'all')
        task_id = data.get('task_id')
        
        with app.app_context():
            if log_type == 'dingtalk':
                if task_id:
                    TaskExecutionLog.query.filter_by(task_id=task_id).delete()
                else:
                    TaskExecutionLog.query.delete()
            elif log_type == 'custom':
                if task_id:
                    CustomTaskLog.query.filter_by(task_id=task_id).delete()
                else:
                    CustomTaskLog.query.delete()
            else:  # all
                TaskExecutionLog.query.delete()
                CustomTaskLog.query.delete()
            
            db.session.commit()
            return jsonify({'success': True, 'message': '日志删除成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 添加缺失的API端点
@app.route('/api/view_file_content')
def view_file_content():
    """查看文件内容"""
    try:
        file_path = request.args.get('path')
        if not file_path:
            return jsonify({'error': '缺少文件路径'}), 400
        
        if not os.path.exists(file_path):
            return jsonify({'error': '文件不存在'}), 404
        
        # 检查文件大小，避免读取过大的文件
        file_size = os.path.getsize(file_path)
        if file_size > 5 * 1024 * 1024:  # 5MB限制
            return jsonify({'error': '文件过大，无法预览（限制5MB）'}), 400
        
        # 尝试以不同编码读取文本文件
        encodings = ['utf-8', 'gbk', 'latin-1', 'cp1252', 'iso-8859-1']
        content = None
        
        for encoding in encodings:
            try:
                with open(file_path, 'r', encoding=encoding) as f:
                    content = f.read()
                break
            except UnicodeDecodeError:
                continue
        
        if content is None:
            app.logger.warning(f"view_file_content接口无法解码文件: {file_path}")
            return jsonify({'error': '文件编码不支持，无法预览'}), 400
        
        return jsonify({'content': content})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/custom_tasks', methods=['GET'])
def get_custom_tasks():
    """获取自定义任务列表"""
    try:
        with app.app_context():
            tasks = CustomTask.query.all()
            task_list = []
            for task in tasks:
                task_list.append({
                    'id': task.id,
                    'name': task.name,
                    'cron_expression': task.cron_expression,
                    'is_active': task.is_active,
                    'last_run_time': task.last_run_time.isoformat() if task.last_run_time else None,
                    'last_run_status': task.last_run_status,
                    'user_id': task.user_id
                })
            return jsonify(task_list)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/custom_tasks', methods=['POST'])
def create_custom_task():
    """创建自定义任务"""
    try:
        data = request.json
        with app.app_context():
            task = CustomTask(
                name=data['name'],
                cron_expression=data['cron_expression'],
                script_id=data['script_id'],
                user_id=data['user_id']
            )
            db.session.add(task)
            db.session.commit()
            return jsonify({'success': True, 'task_id': task.id})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/custom_tasks/<int:task_id>', methods=['PUT'])
def update_custom_task(task_id):
    """更新自定义任务"""
    try:
        data = request.json
        with app.app_context():
            task = CustomTask.query.get(task_id)
            if not task:
                return jsonify({'error': '任务不存在'}), 404
            
            for key, value in data.items():
                if hasattr(task, key):
                    setattr(task, key, value)
            
            db.session.commit()
            return jsonify({'success': True})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/robots', methods=['GET'])
def get_robots():
    """获取机器人列表"""
    try:
        with app.app_context():
            robots = DingTalkRobot.query.all()
            robot_list = []
            for robot in robots:
                robot_list.append({
                    'id': robot.id,
                    'name': robot.name,
                    'webhook': robot.webhook,
                    'secret': robot.secret,
                    'description': robot.description
                })
            return jsonify(robot_list)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/robots', methods=['POST'])
def create_robot():
    """创建机器人"""
    try:
        data = request.json
        with app.app_context():
            robot = DingTalkRobot(
                name=data['name'],
                webhook=data['webhook'],
                secret=data.get('secret', ''),
                description=data.get('description', '')
            )
            db.session.add(robot)
            db.session.commit()
            return jsonify({'success': True, 'robot_id': robot.id})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/robots/<int:robot_id>', methods=['PUT'])
def update_robot(robot_id):
    """更新机器人"""
    try:
        robot = DingTalkRobot.query.get(robot_id)
        if not robot:
            return jsonify({'error': '机器人不存在'}), 404
        
        data = request.get_json()
        robot.name = data.get('name', robot.name)
        robot.tag = data.get('tag', robot.tag)
        robot.webhook = data.get('webhook', robot.webhook)
        robot.secret = data.get('secret', robot.secret)
        
        db.session.commit()
        return jsonify({'message': '机器人更新成功'})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/test_robot', methods=['POST'])
def test_robot():
    """测试钉钉机器人发送"""
    try:
        data = request.get_json()
        webhook = data.get('webhook', '').strip()
        secret = data.get('secret', '').strip()
        
        if not webhook:
            return jsonify({'success': False, 'message': 'Webhook地址不能为空'}), 400
        
        if not secret:
            return jsonify({'success': False, 'message': '加签密钥不能为空'}), 400
        
        # 导入钉钉机器人发送器
        from system_scripts.dingtalk_robot import DingTalkRobotSender
        
        # 创建机器人发送器
        robot_sender = DingTalkRobotSender(webhook, secret)
        
        # 发送测试消息
        test_message = f"🤖 机器人测试消息\n\n⏰ 发送时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n✅ 如果您收到此消息，说明机器人配置正确！"
        
        result = robot_sender.send_text(test_message)
        
        if result.get('errcode') == 0:
            return jsonify({
                'success': True, 
                'message': '测试消息发送成功！请检查钉钉群是否收到消息。'
            })
        else:
            error_msg = result.get('errmsg', '未知错误')
            return jsonify({
                'success': False, 
                'message': f'发送失败: {error_msg}'
            })
            
    except Exception as e:
        app.logger.error(f"测试机器人失败: {str(e)}")
        return jsonify({
            'success': False, 
            'message': f'测试失败: {str(e)}'
        }), 500

@app.route('/api/scripts', methods=['GET'])
def get_scripts():
    """获取脚本列表"""
    try:
        with app.app_context():
            scripts = Script.query.all()
            script_list = []
            for script in scripts:
                script_list.append({
                    'id': script.id,
                    'name': script.name,
                    'description': script.description,
                    'file_path': script.path,
                    'upload_time': script.upload_time.isoformat() if script.upload_time else None
                })
            return jsonify(script_list)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/scripts', methods=['POST'])
def create_script():
    """创建脚本"""
    try:
        data = request.json
        with app.app_context():
            script = Script(
                name=data['name'],
                description=data.get('description', ''),
                path=data['file_path']
            )
            db.session.add(script)
            db.session.commit()
            return jsonify({'success': True, 'script_id': script.id})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/scripts/<int:script_id>', methods=['PUT'])
def update_script(script_id):
    """更新脚本"""
    try:
        data = request.json
        with app.app_context():
            script = Script.query.get(script_id)
            if not script:
                return jsonify({'error': '脚本不存在'}), 404
            
            for key, value in data.items():
                if hasattr(script, key):
                    setattr(script, key, value)
            
            db.session.commit()
            return jsonify({'success': True})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/users', methods=['GET'])
def get_users():
    """获取用户列表"""
    try:
        with app.app_context():
            users = User.query.all()
            user_list = []
            for user in users:
                user_list.append({
                    'id': user.id,
                    'username': user.username,
                    'is_admin': user.is_admin,
                    'created_at': user.created_at.isoformat() if user.created_at else None
                })
            return jsonify(user_list)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/users', methods=['POST'])
def create_user():
    """创建用户"""
    try:
        data = request.json
        with app.app_context():
            user = User(
                username=data['username'],
                password=data['password'],
                is_admin=data.get('is_admin', False)
            )
            db.session.add(user)
            db.session.commit()
            return jsonify({'success': True, 'user_id': user.id})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    """更新用户"""
    try:
        data = request.json
        with app.app_context():
            user = User.query.get(user_id)
            if not user:
                return jsonify({'error': '用户不存在'}), 404
            
            for key, value in data.items():
                if hasattr(user, key) and key != 'password':
                    setattr(user, key, value)
            
            # 处理密码更新
            if 'password' in data:
                from werkzeug.security import generate_password_hash
                # 设置默认密码哈希方法为pbkdf2:sha256，避免使用scrypt（某些系统不支持）
                from functools import partial
                generate_password_hash = partial(generate_password_hash, method='pbkdf2:sha256')
                user.password = generate_password_hash(data['password'])
            
            db.session.commit()
            return jsonify({'success': True})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    # 初始化数据库
    with app.app_context():
        db.create_all()
    
    app.logger.info("执行器服务启动完成")
    
    # 启动Flask应用
    app.run(host='0.0.0.0', port=5002, debug=False)